//
// Created by haoy on 2017/9/22.
//

#include "push_to_talk.hpp"

#include <map>
#include <sstream>

#include "xm_log.h"
#include "xcutil.h"


/*
 * PRIVATE
 */

namespace {

constexpr int max_idle_ms = 100000;

class PushToTalk {
public:
    std::string id;
    rtc_endpoint_t talker;
    std::map<std::string, rtc_endpoint_t> rtcs;
};

std::map<std::string, PushToTalk*> g_ptt_by_id;
std::map<std::string, PushToTalk*> g_ptt_by_rtc;

}

/*
 * PRIVATE
 */
static PushToTalk* _n1a_new(const std::string& id) {
    auto ptt = new PushToTalk();
    std::ostringstream oss;
    ptt->id = id;
    ptt->talker = nullptr;
    return ptt;
}

static void _ptt_delete(PushToTalk *ptt) {
    if (ptt->talker) {
        for (auto rtc_it : ptt->rtcs) {
            auto rtc = rtc_it.second;
            if (ptt->talker != rtc) {
                rtc_endpoint_unsubscribe(rtc, ptt->talker);
            }
        }
    }
    delete ptt;
}

static void _ptt_remove(PushToTalk *ptt, rtc_endpoint_t rtc) {
    dbgd("_ptt_remove: n-to-1 audio connector %s remove rtc %s", ptt->id.c_str(), rtc_endpoint_get_id(rtc));
    if (ptt->talker == rtc) {
        for (auto follower_it : ptt->rtcs) {
            // auto& follower_id = follower_it.first;
            auto& follower = follower_it.second;
            if (follower != rtc) {
                rtc_endpoint_unsubscribe(follower, rtc);
            }
        }
        ptt->talker = nullptr;
    } else {
        if (ptt->talker) {
            rtc_endpoint_unsubscribe(rtc, ptt->talker);
        }
    }
    ptt->rtcs.erase(std::string(rtc_endpoint_get_id(rtc)));
    if (ptt->rtcs.empty()) {
        dbgd("_ptt_remove: connector=%s has 0 rtc, idle timer started", ptt->id.c_str());
    } else {
        dbgd("_ptt_remove: connector=%s has %lu rtc", ptt->id.c_str(), ptt->rtcs.size());
    }
}

int push_to_talk_join(const std::string &id, rtc_endpoint_t rtc) {
    dbgi("push_to_talk_join: connector=%s rtc=%s", id.c_str(), rtc_endpoint_get_id(rtc));
    PushToTalk* ptt = nullptr;
    // one rtc can only join one ptt
    auto it = g_ptt_by_rtc.find(std::string(rtc_endpoint_get_id(rtc)));
    if (it != g_ptt_by_rtc.end()) {
        ptt = it->second;
        if (ptt->id != id) {
            dbge("push_to_talk_join: err rtc %s already in connector %s", rtc_endpoint_get_id(rtc), ptt->id.c_str());
            return -100;
        }
    }

    auto sit = g_ptt_by_id.find(id);
    if (sit != g_ptt_by_id.end()) {
        ptt = sit->second;
    }
    if (!ptt) {
        ptt = _n1a_new(id);
        g_ptt_by_id.emplace(id, ptt);
        dbgi("push_to_talk_join: created n-to-1 audio connector %s", id.c_str());
    }
    ptt->rtcs.emplace(std::string(rtc_endpoint_get_id(rtc)), rtc);
    if (ptt->talker) {
        rtc_endpoint_subscribe(rtc, ptt->talker, true, false);
    }
    g_ptt_by_rtc.emplace(std::string(rtc_endpoint_get_id(rtc)), ptt);
    return 0;
}

int push_to_talk_talk(const std::string &id, rtc_endpoint_t rtc) {
    auto ptt_it = g_ptt_by_id.find(id);
    if (ptt_it == g_ptt_by_id.end()) {
        dbge("push_to_talk_talk: err connector %s not found", id.c_str());
        return -100;
    }
    auto ptt = ptt_it->second;
    if (ptt->rtcs.count(std::string(rtc_endpoint_get_id(rtc))) == 0) {
        dbge("push_to_talk_talk: err rtc %s not in connector %s", rtc_endpoint_get_id(rtc), id.c_str());
        return -200;
    }
    if (ptt->talker != rtc) {
        if (ptt->talker) {
            for (auto listener_it : ptt->rtcs) {
                auto& listener = listener_it.second;
                if (listener != ptt->talker && rtc_endpoint_get_publisher(listener) != rtc) {
                    rtc_endpoint_unsubscribe(listener, ptt->talker);
                }
            }
        }

        for (auto listener_it : ptt->rtcs) {
            auto& listener = listener_it.second;
            if (listener != rtc && rtc_endpoint_get_publisher(listener) != rtc) {
                rtc_endpoint_subscribe(listener, rtc, true, false);
            }
        }
        ptt->talker = rtc;
    }
    return 0;
}

//void push_to_talk_on_tick(uint64_t now) {
//    std::list<PushToTalk*> timeout_ptt_list;
//    for (auto ptt_it : g_ptt_by_id) {
//        auto ptt = ptt_it.second;
//        if (ptt->idle_since != 0 && now - ptt->idle_since > max_idle_ms) {
//            dbgi("push-to-talk %s timeout, will be removed immediately", ptt->id.c_str());
//            timeout_ptt_list.emplace_back(ptt);
//        }
//    }
//    for (auto ptt : timeout_ptt_list) {
//        for (auto rtc_it : ptt->rtcs) {
//            auto& rtc = rtc_it.second;
//            g_ptt_by_rtc.erase(std::string(rtc_endpoint_get_id(rtc)));
//        }
//        g_ptt_by_id.erase(ptt->id);
//        _ptt_delete(ptt);
//    }
//}

void push_to_talk_on_rtc_destroy(rtc_endpoint_t rtc) {
    auto it = g_ptt_by_rtc.find(std::string(rtc_endpoint_get_id(rtc)));
    if (it == g_ptt_by_rtc.end()) {
        return;
    }
    auto ptt = it->second;
    _ptt_remove(ptt, rtc);
    if (ptt->rtcs.size() == 0) {
        g_ptt_by_id.erase(ptt->id);
        g_ptt_by_rtc.erase(std::string(rtc_endpoint_get_id(rtc)));
        _ptt_delete(ptt);
    }
}

